import expressions

class Widget(object):
  def __init__(self, defenition_line=None):
    self._size = [None, None]
    self._box_size = [None, None]
    self._defenition_line = defenition_line
    # Position of the top left corner (top, left)
    self._absolute_position = (0, 0)

  def scrollbars_needed(self):
    '''Returns a touple of weather scrollbars are needed (horizontal, vertical).
    
    If sizes are unknown, False would be returned.'''
    try:
      return (self.calculate_size()[0] > self.calculate_box_size()[0],
              self.calculate_size()[1] > self.calculate_box_size()[1])
    except TypeError:
      return False

  def is_sized(self):
    return self._box_size[0].isReadonly(), self._box_size[1].isReadonly()

  def calculate_size(self):
    return tuple(self._box_size[i].calculateValue() if self._size[i] is None else self._size[i].calculateValue()
        for i in xrange(2))

  def calculate_box_size(self):
    return tuple(x.calculateValue() for x in self._box_size)

  def get_box_size_expressions(self):
    return self._box_size

  def get_widget_edges(self):
    '''Returns the edges of the widget as (top, right, bottom, left)'''
    box_width, box_height = self.calculate_box_size()
    width, height = self.calculate_size()
    h_offset = int(max(0, box_width - width) / 2)
    v_offset = int(max(0, box_height - height) / 2)
    edges = self.__get_edges(False)
    return (edges[0] + v_offset,
            edges[1] + h_offset,
            edges[2] + v_offset,
            edges[3] + h_offset)

  def get_box_edges(self):
    '''Returns the edges of the box as (top, right, bottom, left)'''
    return self.__get_edges(True)

  def update_size(self, width, height):
    if width is not None:
      self._size[0] = width
    if height is not None:
      self._size[1] = height
    self.update_box_size(width, height)

  def update_box_size(self, width, height):
    if width is not None:
      self._box_size[0] = width
    if height is not None:
      self._box_size[1] = height

  def __get_edges(self, is_box):
    try:
      return (self._absolute_position[0],
              self._absolute_position[1] + (self.calculate_box_size()[0] if is_box else self.calculate_size()[0]),
              self._absolute_position[0] + (self.calculate_box_size()[1] if is_box else self.calculate_size()[1]),
              self._absolute_position[1])
    except TypeError:
      raise ValueError, "Size couldn't be determined for widget defined on line %s" % self._defenition_line

  def recalculate_size(self):
    self._box_size = [None, None]
    self.update_size(*self._size)

  def draw(self, window):
    '''Draws the widget on the screen.
    
    This implementation only checks validity of a widget. Draw instructions MUST be implmemented per Atom.'''
    if None in self.calculate_size():
      raise ValueError, "Size couldn't be determined for widget defined on line %s" % self._defenition_line


class DirectionalComposition(Widget):
  def __init__(self, direction, *args, **kwargs):
    '''Ctor. Created with the direction of this composition (horizontal=0, vertical=1)'''
    super(DirectionalComposition, self).__init__(*args, **kwargs)
    self.direction = direction
    self._widgets_list = []
    self._origWidth = None
    self._origHeight = None

  def __len__(self):
    return len(self._widgets_list)

  def __getitem__(self, index):
    return self._widgets_list[index]

  def update_box_size(self, width, height):
    super(DirectionalComposition, self).update_box_size(width, height)
    l = [width, height]
    if l[self.direction] is not None:
      widgets_to_update = []
      size_so_far_str = "0"
      variables = l[self.direction].getVariables()
      for widget in self._widgets_list:
        if widget._box_size[self.direction] is None:
          widgets_to_update.append(widget)
        else:
          size_so_far_str += " + " + str(widget._box_size[self.direction].expression)
          variables.update(widget._box_size[self.direction].getVariables())
      remaining_size_str = "(%s) - (%s)" % (str(l[self.direction].expression), size_so_far_str)
      size_strs = ["max(0, ((%s)//%d))" % (remaining_size_str, len(widgets_to_update))] * len(widgets_to_update)
      if len(widgets_to_update) > 1:
        size_strs[0] = "max(0, ((%s)-((%s)//%d)*%d))" % (
            remaining_size_str, remaining_size_str, len(widgets_to_update), len(widgets_to_update)-1)
      for widget, size_str in zip(widgets_to_update, size_strs):
        size_expression = expressions.Expression(size_str, variables)
        k = [width, height]
        k[self.direction] = size_expression
        widget.update_box_size(*k)
    l[self.direction] = None
    for widget in self._widgets_list:
      widget.update_box_size(*l)

  def add_to_list(self, widget):
    update_size_index = 0
    if self.direction == 0:
      update_size_index = 1

    self._widgets_list.append(widget)

    if len(self._widgets_list) == 1:
      l = [None, None]
      l[self.direction] = widget._box_size[self.direction]
      super(DirectionalComposition, self).update_size(*l)
      self.update_box_size(*widget._box_size)
      return

    if self._size[self.direction] is not None:
      if widget._box_size[self.direction] is not None:
        variables = self._size[self.direction].getVariables()
        variables.update(widget._box_size[self.direction].getVariables())
        new_expression = expressions.Expression("%s + %s" % (
            self._size[self.direction].expression, widget._box_size[self.direction].expression), variables)
        self._size[self.direction] = new_expression
        self._box_size[self.direction] = new_expression
      else:
        self._size[self.direction] = None
        self._box_size[self.direction] = None

    l = [None, None]
    if widget._box_size[update_size_index] is not None and self._box_size[update_size_index] is not None:
      variables = widget._box_size[update_size_index].getVariables()
      variables.update(self._box_size[update_size_index].getVariables())
      l[update_size_index] = expressions.Expression("max((%s), (%s))" % (
          widget._box_size[update_size_index].expression, self._box_size[update_size_index].expression), variables)
    elif widget._box_size[update_size_index] is not None:
      l[update_size_index] = widget._box_size[update_size_index]
    elif self._box_size[update_size_index] is not None:
      l[update_size_index] = self._box_size[update_size_index]
    self.update_box_size(*l)

  def update_size(self, width, height):
    self._origWidth = width
    self._origHeight = height
    l = [width, height]
    if self._size[self.direction] is not None:
      l[self.direction] = None
    if l[0] is not None:
      self._size[0] = l[0]
    if l[1] is not None:
      self._size[1] = l[1]
    self.update_box_size(width, height)

  def recalculate_size(self):
    widgets = self._widgets_list
    self._widgets_list = []
    self._size = [None, None]
    self._box_size = [None, None]
    for widget in widgets:
      widget.recalculate_size()
      self.add_to_list(widget)
    self.update_size(self._origWidth, self._origHeight)

  def draw(self, window):
    super(DirectionalComposition, self).draw(window)
    draw_at = self.get_widget_edges()
    next_edge = [draw_at[0], draw_at[3]]
    for widget in self._widgets_list:
      widget._absolute_position = tuple(next_edge)
      next_edge[0 if self.direction == 1 else 1] += widget.calculate_size()[self.direction]
      widget.draw(window)
